package merrors

import (
	"context"
	"errors"
	"fmt"

	"wxpay/pkg/mcontext"
	"wxpay/pkg/util"

	"google.golang.org/genproto/googleapis/rpc/errdetails"
	"google.golang.org/grpc/status"

	"go.uber.org/zap"
	"google.golang.org/grpc"

	"google.golang.org/grpc/codes"
)

func LogPanic(ctx context.Context, caller string, req interface{}, info *grpc.UnaryServerInfo, err *error) {
	p := recover()
	logger := mcontext.Logger(ctx)
	if p != nil {
		logger.Error(caller+" panic", zap.Any("info", info), zap.Any("ctx", ctx), zap.Any("req", req),
			zap.Any("panic", p), zap.String("stack", util.GetStackInfo()))
		*err = ErrUnknown
	}
}

type Op string
type Kind codes.Code

const (
	Other           Kind = iota // Unclassified error. This value is not printed in the error message.
	Invalid                     // Invalid operation for this type of item.
	Permission                  // Permission denied.
	IO                          // External I/O error such as network failure.
	Exist                       // Item already exists.
	NotExist                    // Item does not exist.
	NotEmpty                    // Directory not empty.
	NotImp                      // Invoke not implement.
	Private                     // Information withheld.
	Internal                    // Internal error or inconsistency.
	CannotDecrypt               // No wrapped key for user with read access.
	Transient                   // A transient error.
	BrokenLink                  // Link target does not exist.
	DBError                     // db error
	InUse                       // 存在关联
	EventSendFailed             // 发送消息失败
	NetworkFailed               // 网络错误
	InvalidArgument             // 参数错误
	StatusInvalid               // 状态错误
	TimeOut                     // 网络超时
)

func (k Kind) String() string {
	switch k {
	case Other:
		return "other error"
	case Invalid:
		return "invalid operation"
	case Permission:
		return "permission denied"
	case IO:
		return "I/O error"
	case Exist:
		return "item already exists"
	case NotExist:
		return "NOT_FOUND"
	case BrokenLink:
		return "link target does not exist"
	case NotEmpty:
		return "directory not empty"
	case NotImp: 
		return "invoke not implement"
	case Private:
		return "information withheld"
	case Internal:
		return "internal error"
	case CannotDecrypt:
		return `no wrapped key for user; owner must "upspin share -fix"`
	case Transient:
		return "transient error"
	case DBError:
		return "db error"
	case InUse:
		return "related resource in use"
	case EventSendFailed:
		return "msg sent failed"
	case NetworkFailed:
		return "network failed"
	case InvalidArgument:
		return "INVALID_ARGUMENTS"
	case StatusInvalid:
		return "INVALID_STATUS"
	case TimeOut:
		return "TIMEOUT"
	}
	return "ERROR_UNKNOWN"
}

type ErrorCode int

const (
	ErrDefault ErrorCode = iota
	ErrNotFound
	ErrStatusInValid
	ErrInvalidArguments
	ErrInternal
	ErrAcquireLock
	ErrMessageSent
	ErrPermission
	ErrAlreadyExist
	ErrRemainNotEnough
	ErrTimeOut
	ErrCosClient
	ErrOssClient
	ErrWxApi
	ErrRedisClient
	ErrDB
	ErrAlipayApi
)

func (k ErrorCode) String() string {
	switch k {
	case ErrNotFound:
		return "NOT_FOUND"
	case ErrStatusInValid:
		return "INVALID_STATUS"
	case ErrDefault:
		return "ERROR_UNKNOWN"
	case ErrInvalidArguments:
		return "INVALID_ARGUMENTS"
	case ErrInternal:
		return "INTERNAL"
	case ErrAcquireLock:
		return "ERROR_ACQUIRE_LOCK"
	case ErrMessageSent:
		return "ERROR_MESSAGE_SENT"
	case ErrPermission:
		return "PERMISSION_DENIED"
	case ErrAlreadyExist:
		return "ALREADY_EXIST"
	case ErrRemainNotEnough:
		return "REMAIN_NOT_ENOUGH"
	case ErrTimeOut:
		return "TIME_OUT"
	case ErrCosClient:
		return "COS_CLIENT_ERROR"
	case ErrOssClient:
		return "OSS_CLIENT_ERROR"
	case ErrWxApi:
		return "WX_API_ERROR"
	case ErrRedisClient:
		return "REDIS_ERROR"
	case ErrDB:
		return "DB_ERROR"
	case ErrAlipayApi:
		return "Alipay_API_ERROR"
	}
	return "ERROR_UNKNOWN"
}

func Ops(e *Error) []Op {
	res := []Op{e.Op}

	subErr, ok := e.Err.(*Error)
	if !ok {
		return res
	}
	res = append(res, Ops(subErr)...)
	return res
}

type Error struct {
	Op   Op
	Kind Kind
	Err  error
	code ErrorCode
}

func (e *Error) Error() string {
	return fmt.Sprintf("%s-%s-%v", Ops(e), e.Kind, e.Err)
}

func (e *Error) CustomerErrorMsg() string {
	if v, ok := e.Cause().(*Error); ok {
		return v.Err.Error()
	} else {
		return v.Error()
	}
}

func (e *Error) CustomerErrorCode() string {
	var errCode ErrorCode
	if v, ok := e.Cause().(*Error); ok {
		errCode = v.GetErrorCode()
	}
	return errCode.String()
}

func (e *Error) GetErrorCode() ErrorCode {
	return e.code
}

// 返回堆栈中第一个错误
func (e *Error) Cause() error {
	subErr, ok := e.Err.(*Error)
	if !ok {
		return e
	}
	return subErr.Cause()
}

var ErrorsShouldNotify = map[Kind]bool{
	Other:           true,
	Internal:        true,
	DBError:         true,
	EventSendFailed: true,
}

func (e *Error) ToGrpcCode() codes.Code {
	switch v := e.Cause().(type) {
	case *Error:
		switch v.Kind {
		case Other:
			return codes.Unknown
		case Invalid:
			return codes.InvalidArgument
		case Permission:
			return codes.PermissionDenied
		case IO:
			return codes.Internal
		case Exist:
			return codes.AlreadyExists
		case NotExist:
			return codes.NotFound
		case Private:
			return codes.Unknown
		case Internal:
			return codes.Internal
		case CannotDecrypt:
			return codes.InvalidArgument
		case Transient:
			return codes.Internal
		case BrokenLink:
			return codes.Internal
		case DBError:
			return codes.Internal
		case InvalidArgument:
			return codes.InvalidArgument
		case InUse:
			return codes.Unknown
		case StatusInvalid:
			return codes.PermissionDenied
		case TimeOut:
			return codes.Unknown
		}
	}
	return codes.Unknown
}

func E(args ...interface{}) error {
	e := &Error{}
	for _, arg := range args {
		switch arg := arg.(type) {
		case Op:
			e.Op = arg
		case Kind:
			e.Kind = arg
		case error:
			e.Err = arg
		case string:
			e.Err = errors.New(arg)
		case ErrorCode:
			e.code = arg
		default:
			panic("不支持此参数")
		}
	}
	// 保护性代码，防止Err为空
	if e.Err == nil {
		e.Err = ErrUnknown
	}
	return e
}

func (e *Error) NotExist() bool {
	return e.Kind == NotExist
}

type notExist interface {
	NotExist() bool
}

type errIdempotent interface {
	IsIdempotent() bool
}

func (e *Error) IsStatusInvalid() bool {
	return e.Kind == StatusInvalid
}

func (e *Error) IsTimeOut() bool {
	return e.Kind == TimeOut
}

type timeOut interface {
	IsTimeOut() bool
}

// 获取status.details中的ErrorInfo
func GetGrpcErrorInfo(err error) *errdetails.ErrorInfo {
	if s, ok := status.FromError(err); ok && s != nil {
		for _, d := range s.Details() {
			switch t := d.(type) {
			case *errdetails.ErrorInfo:
				return t
			}
		}
	}
	return nil
}

const bizErrorReason string = "BizError"

func IsBizError(e *errdetails.ErrorInfo) bool {
	return e != nil && e.Reason == bizErrorReason
}
